"""
Generates the neural network
"""

from Neuron import *
from Constants import *
import random
import math
import pickle

# Network statistics variables
num_exc_neurons = 0.0
num_inh_neurons = 0.0
num_exc_connect = 0.0
num_inh_connect = 0.0
avg_exc_connect = 0.0
avg_inh_connect = 0.0

# Network stuff
neurons = list()
connxs  = list()

def pick_long_range( first ):
    sin_phi = math.sin( first.phi )
    sin_theta = math.sin( first.theta )
    cos_phi = math.cos( first.phi )
    cos_theta = math.cos( first.theta )
    
    # Random angle
    u = 2.0*math.pi*random.random()
    
    # (x,y) Coordinates of center of long-distance neighbordhood; z determined from x and y
    x = 3.0*math.sqrt( 7 )*math.cos( u )*(sin_phi**2.0*cos_theta + cos_phi**2.0) + 3.0*math.sqrt( 7 )*math.sin( u )*cos_phi*sin_phi*(1.0-cos_theta) - 1.0*(-sin_phi*sin_theta)
    y = 3.0*math.sqrt( 7 )*math.cos( u )*cos_phi*sin_phi*(1.0-cos_theta) + 3.0*math.sqrt( 7 )*math.sin( u )*( cos_phi**2.0*cos_theta + sin_phi**2.0 ) - 1.0*(cos_phi*sin_theta)
    z = 3.0*math.sqrt( 7 )*math.cos( u )*sin_phi*sin_theta + 3.0*math.sqrt( 7 )*math.sin( u)*(-cos_phi*sin_theta) - 1.0*cos_theta

    first.far_theta = math.acos(z/8.0)
    first.far_phi = math.atan2(y, x)
    
# Determine the shortest distance between two neurons on the surface of the sphere
def distance( first, second ):
    return SPHERE_RADIUS * 2.0 * math.asin( math.sqrt( math.sin( ( first.theta - second.theta ) / 2.0 )**2.0 + math.cos( first.theta ) * math.cos( second.theta ) * math.sin( ( first.phi - second.phi ) / 2.0 )**2.0 ) )
    
# Determine the shortest distance between a neuron on the surface of the sphere and any input point
def far_distance( first, pt_theta, pt_phi ):
    return SPHERE_RADIUS * 2.0 * math.asin( math.sqrt( math.sin( ( first.theta - pt_theta ) / 2.0 )**2.0 + math.cos( first.theta ) * math.cos( pt_theta ) * math.sin( ( first.phi - pt_phi ) / 2.0 )**2.0 ) )
    
# Determine the (square) distance between two neurons in 3-space
def distance_three_space( first, second ):
    x1 = SPHERE_RADIUS * math.cos( first.phi ) * math.sin( first.theta )
    y1 = SPHERE_RADIUS * math.sin( first.phi ) * math.sin( first.theta )
    z1 = SPHERE_RADIUS * math.cos( first.phi )
    
    x2 = SPHERE_RADIUS * math.cos( second.phi ) * math.sin( second.theta )
    y2 = SPHERE_RADIUS * math.sin( second.phi ) * math.sin( second.theta )
    z2 = SPHERE_RADIUS * math.cos( second.phi )
    
    return (x1-x2)**2 + (y1-y2)**2 + (z1 - z2)**2
    
def far_distance_three_space( first, pt_theta, pt_phi ):
    x1 = SPHERE_RADIUS * math.cos( first.phi ) * math.sin( first.theta )
    y1 = SPHERE_RADIUS * math.sin( first.phi ) * math.sin( first.theta )
    z1 = SPHERE_RADIUS * math.cos( first.phi )
    
    x2 = SPHERE_RADIUS * math.cos( pt_phi ) * math.sin( pt_theta )
    y2 = SPHERE_RADIUS * math.sin( pt_phi ) * math.sin( pt_theta )
    z2 = SPHERE_RADIUS * math.cos( pt_phi )
    
    return (x1-x2)**2 + (y1-y2)**2 + (z1 - z2)**2


# Make the list of neurons
for neuron in range( NUM_NEURONS ):
    neurons.append( Neuron() )
    
# Populate that list with randomized parameters and positions in space on the sphere
for neuron in neurons:

    # Set positions uniformly on cortical surface.
    neuron.theta = math.acos( 2.0 * random.random() - 1.0 ) # Theta \in \left[0, Pi)
    neuron.phi = 2.0 * math.pi * random.random()  # Phi \in \left[0, 2Pi)

    # The neuron is excitatory
    if random.random() <= EXC_PROB:
        neuron.exc = True
        neuron.a = 0.02
        neuron.b = 0.2
        neuron.c = -65.0 + 15.0 * random.random()**2
        neuron.d = 8.0 - 6.0 * random.random()**2
        neuron.U = 0.05
        neuron.F = 1000.0
        neuron.D = 800.0
        pick_long_range( neuron )
        num_exc_neurons = num_exc_neurons + 1.0
        neuron.R = 1.0
        neuron.w = neuron.U

    # The neuron is inhibitory
    else:
        neuron.exc = False
        neuron.a = 0.02 + 0.08 * random.random()
        neuron.b = 0.25 - 0.05 * random.random()
        neuron.c = -65.0
        neuron.d = 2.0
        neuron.U = 0.2
        neuron.F = 20.0
        neuron.D = 700.0
        num_inh_neurons = num_inh_neurons + 1.0
        neuron.R = 0.5
        neuron.w = neuron.U
        
print "Generating distance matrix the tedious way..."
distance_matrix = NUM_NEURONS * [None]
for row in range( NUM_NEURONS ):
    distance_matrix[row] = NUM_NEURONS * [0]
    for col in range( NUM_NEURONS ):
        if row != col:
            distance_matrix[ row ][ col ] = distance_three_space( neurons[ row ], neurons[ col ] )
            # print distance_matrix[ row ][ col ]
            if neurons[row].exc == True:
                if distance_matrix[row][col] >= 1.5**2.0: # mm
                    distance_matrix[row][col] = far_distance( neurons[ col ], neurons[ row ].far_theta, neurons[ col ].far_phi )
                    if distance_matrix[row][col] >= 0.5**2.0: # mm
                        distance_matrix[row][col] = 0
                    else:
                        distance_matrix[row][col] = int(math.ceil(math.sqrt(distance_matrix[row][col])/( HIGH_SPEED * DELTA_T ) + 12.0/( LOW_SPEED * DELTA_T )))
                else:
                    distance_matrix[ row ][ col ] = int(math.ceil(math.sqrt(distance_matrix[ row ][ col ])/( HIGH_SPEED * DELTA_T)))
                    # print distance_matrix[ row ][ col ]
            else:
                if distance_matrix[row][col] >= 0.5**2.0: # mm
                    distance_matrix[row][col] = 0
                else:
                    distance_matrix[ row ][ col ] = int(math.ceil(math.sqrt(distance_matrix[ row ][ col ])/( HIGH_SPEED * DELTA_T )))

print "Using distance matrix to generate neighbors lists..."
for row in range( NUM_NEURONS):
    for col in range( NUM_NEURONS ):
        if distance_matrix[row][col] != 0:
            # Append the neighbor to the source neuron
            n = Neighbor()
            n.source = neurons[row]
            n.destination = neurons[col]
            n.weight = INIT_WEIGHT
            n.ddt_weight = 1.0
            n.distance = distance_matrix[row][col]
            neurons[row].neighbors.append(n)

            # Append the neighbor to the destination neuron symmetrically
            neurons[col].neighbors.append(n)

# Pickle the neuron list
pickle.dump( neurons, open( "neurons.list", "wb" ) )
